package com.ehsunbehravesh.service.security.ejb.encryption;

import com.ehsunbehravesh.utils.BlockRSA;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;

/**
 * Encryption business implementation.
 *
 * @author ehsun.behravesh
 */
@Stateless(name = "EncryptionEJB")
@Local(EncryptionLocalBusiness.class)
@Remote(EncryptionRemoteBusiness.class)
public class EncryptionBean implements EncryptionLocalBusiness, EncryptionRemoteBusiness {

  private static final String RSA_ALGORITHM = "RSA";
  private static final String AES_ALGORITHM = "AES";
  private static final String BLOWFISH_ALGORITHM = "Blowfish";
  private KeyPairGenerator keyPairGenerator;

  @PostConstruct
  public void initialize() {
    try {
      keyPairGenerator = createKeyPairGenerator();
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

  private KeyPairGenerator createKeyPairGenerator() throws NoSuchAlgorithmException {
    return KeyPairGenerator.getInstance(RSA_ALGORITHM);
  }

  @Override
  public byte[] encryptRSA(final Key key, final byte[] data) throws EncryptionServiceException {
    try {            
      Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
      cipher.init(Cipher.ENCRYPT_MODE, key);
      return cipher.doFinal(data);
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IllegalBlockSizeException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (BadPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (InvalidKeyException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public byte[] decryptRSA(final Key key, final byte[] encrypted) throws EncryptionServiceException {
    try {
      Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
      cipher.init(Cipher.DECRYPT_MODE, key);
      return cipher.doFinal(encrypted);
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (InvalidKeyException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IllegalBlockSizeException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (BadPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public byte[] encryptAES(final byte[] key, final byte[] data) throws EncryptionServiceException {
    try {
      Cipher c = Cipher.getInstance(AES_ALGORITHM);
      SecretKeySpec k = new SecretKeySpec(key, AES_ALGORITHM);
      c.init(Cipher.ENCRYPT_MODE, k);
      byte[] byteEncrypted = c.doFinal(data);

      return byteEncrypted;
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IllegalBlockSizeException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (BadPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (InvalidKeyException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public byte[] decryptAES(final byte[] key, final byte[] encrypted) throws EncryptionServiceException {
    try {
      Cipher c = Cipher.getInstance(AES_ALGORITHM);
      SecretKeySpec k = new SecretKeySpec(key, AES_ALGORITHM);
      c.init(Cipher.DECRYPT_MODE, k);
      byte[] byteText = c.doFinal(encrypted);

      return byteText;
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (InvalidKeyException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IllegalBlockSizeException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (BadPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public KeyPair generateRSAKeyPair(final int size) {
    keyPairGenerator.initialize(size);
    return keyPairGenerator.generateKeyPair();
  }

  @Override
  public PublicKey createRSAPublicKey(final byte[] key) throws EncryptionServiceException {
    try {
      PublicKey result = KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(new X509EncodedKeySpec(key));
      return result;
    } catch (InvalidKeySpecException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public PrivateKey createRSAPrivateKey(final byte[] key) throws EncryptionServiceException {
    try {
      PrivateKey result = KeyFactory.getInstance(RSA_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(key));      
      return result;
    } catch (InvalidKeySpecException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public SecretKey generateAESKey(int bits) throws EncryptionServiceException {
    try {
      KeyGenerator kgen = KeyGenerator.getInstance(AES_ALGORITHM);
      kgen.init(bits); // 192 and 256 bits may not be available
      SecretKey key = kgen.generateKey();
      return key;
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public SecretKey generateBlowfishKey(int bits) throws EncryptionServiceException {
    try {
      KeyGenerator generator = KeyGenerator.getInstance(BLOWFISH_ALGORITHM);
      generator.init(bits);
      SecretKey key = generator.generateKey();
      return key;
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public byte[] encryptBlowfish(byte[] key, byte[] data) throws EncryptionServiceException {
    try {
      Cipher c = Cipher.getInstance(BLOWFISH_ALGORITHM);
      SecretKeySpec k = new SecretKeySpec(key, BLOWFISH_ALGORITHM);
      c.init(Cipher.ENCRYPT_MODE, k);
      byte[] byteEncrypted = c.doFinal(data);

      return byteEncrypted;
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (InvalidKeyException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IllegalBlockSizeException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (BadPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public byte[] decryptBlowfish(byte[] key, byte[] encrypted) throws EncryptionServiceException {
    try {
      Cipher c = Cipher.getInstance(BLOWFISH_ALGORITHM);
      SecretKeySpec k = new SecretKeySpec(key, BLOWFISH_ALGORITHM);
      c.init(Cipher.DECRYPT_MODE, k);
      byte[] byteText = c.doFinal(encrypted);

      return byteText;
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IllegalBlockSizeException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (BadPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (InvalidKeyException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public byte[] encryptRSABlock(final Key key, final byte[] data, final int keySize) throws EncryptionServiceException {
    try {
      BlockRSA cipher = new BlockRSA(keySize);
      byte[] result = cipher.encryptBlock(key, data);
      return result;
    } catch (IllegalBlockSizeException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (InvalidKeyException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (BadPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (UnsupportedEncodingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IOException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }

  @Override
  public byte[] decryptRSABlock(final Key key, final byte[] encrypted, final int keySize) throws EncryptionServiceException {
    try {
      BlockRSA cipher = new BlockRSA(keySize);
      byte[] result = cipher.decryptBlock(key, encrypted);
      return result;
    } catch (UnsupportedEncodingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IOException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchAlgorithmException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (NoSuchPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (InvalidKeyException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (IllegalBlockSizeException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    } catch (BadPaddingException ex) {
      Logger.getLogger(EncryptionBean.class.getName()).log(Level.SEVERE, null, ex);
      throw new EncryptionServiceException(ex);
    }
  }
  
  @Override
  public int getRSAKeySize(RSAKey key) {
    return key.getModulus().bitLength();
  }

}
